﻿using BizTalk.ESB.Extensions.SSO.Common;
using Microsoft.EnterpriseSingleSignOn.Interop;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Xml;
using System.Xml.Serialization;

namespace BizTalk.ESB.Extensions.SSO.Data
{
    public class SsoConfigStore
    {
        internal const string identifierGuid = "EsbConfig";
        private static byte[] _salt = System.Text.Encoding.ASCII.GetBytes("A7B1147D6681380024E56127DCAB3549");

        public List<SsoApplication> ListCustomApplications(bool IncludeProperties)
        {
            string[] ApplicationNames, UserGroups, AdminGroups, Descriptions, Contacts;
            int[] Flags;
            ISSOMapper2 ssoMapper = new ISSOMapper2();
            IPropertyBag propBag = (IPropertyBag)ssoMapper;

            var appTypes = AffiliateApplicationType.ConfigStore;
            uint appFilterFlagMask = SSOFlag.SSO_FLAG_APP_FILTER_BY_TYPE;
            uint appFilterFlags = (uint)appTypes;

            object appFilterFlagsObj = (object)appFilterFlags;
            object appFilterFlagMaskObj = (object)appFilterFlagMask;

            propBag.Write("AppFilterFlags", ref appFilterFlagsObj);
            propBag.Write("AppFilterFlagMask", ref appFilterFlagMaskObj);

            ssoMapper.GetApplications2(out ApplicationNames, out Descriptions, out Contacts, out UserGroups, out AdminGroups, out Flags);

            List<SsoApplication> results = new List<SsoApplication>();
            for (int i = 0; i < ApplicationNames.Length; i++)
            {
                if (Contacts[i].ToUpper() != "SOMEONE@MICROSOFT.COM" && ApplicationNames[i].StartsWith(identifierGuid))
                {
                    SsoApplication app = new SsoApplication();
                    app.AdminGroup = AdminGroups[i];
                    app.ApplicationName = ApplicationNames[i].Substring(ApplicationNames[i].IndexOf("_") + 1);
                    app.Description = Descriptions[i];
                    app.UserGroup = UserGroups[i];
                    if (IncludeProperties)
                    {
                        app.PropertyBag = GetSsoApplication(ApplicationNames[i]).PropertyBag;
                    }
                    results.Add(app);
                }
            }
            return results;
        }

        public void CreateSsoApplication(SsoApplication Application)
        {
            if (AppExists(Application.ApplicationName))
            {
                UpdateSsoApplication(Application);
            }
            else
            {
                int appFlags = 0;
                string prefixedApplicationName;
                if (Application.ApplicationName.StartsWith(identifierGuid))
                {
                    prefixedApplicationName = Application.ApplicationName;
                }
                else
                {
                    prefixedApplicationName = identifierGuid + "_" + Application.ApplicationName;
                }

                //bitwise operation for flags
                appFlags |= SSOFlag.SSO_FLAG_APP_CONFIG_STORE;
                appFlags |= SSOFlag.SSO_FLAG_SSO_WINDOWS_TO_EXTERNAL;
                appFlags |= SSOFlag.SSO_FLAG_APP_ALLOW_LOCAL;

                ISSOAdmin ssoAdmin = new ISSOAdmin();
                ssoAdmin.CreateApplication(prefixedApplicationName, Application.Description, String.Empty, Application.UserGroup, Application.AdminGroup, appFlags, Application.PropertyBag.Count);

                //create property fields
                int counter = 0;

                //create dummy field in first slot
                ssoAdmin.CreateFieldInfo(prefixedApplicationName, "dummy", 0);

                foreach (var item in Application.PropertyBag)
                {
                    //create properties - this does not set the values
                    ssoAdmin.CreateFieldInfo(prefixedApplicationName, item.ConfigurationKey, item.ConfigurationFlags);
                    counter++;
                }

                //enable application
                ssoAdmin.UpdateApplication(prefixedApplicationName, Application.Description, String.Empty, Application.UserGroup, Application.AdminGroup, SSOFlag.SSO_FLAG_ENABLED, SSOFlag.SSO_FLAG_ENABLED);

                //set field values
                SetConfigProperties(Application.ApplicationName, Application.PropertyBag);
            }
        }

        public void UpdateSsoApplication(SsoApplication Application)
        {
            string prefixedApplicationName;
            if (Application.ApplicationName.StartsWith(identifierGuid))
            {
                prefixedApplicationName = Application.ApplicationName;
            }
            else
            {
                prefixedApplicationName = identifierGuid + "_" + Application.ApplicationName;
            }
            
            SetConfigProperties(prefixedApplicationName, Application.PropertyBag);
            var ssoAdmin = new SSOAdmin();
            ((ISSOAdmin)ssoAdmin).UpdateApplication(prefixedApplicationName, Application.Description, String.Empty, Application.UserGroup, Application.AdminGroup, SSOFlag.SSO_FLAG_ENABLED, SSOFlag.SSO_FLAG_ENABLED);
        }

        public void ReplaceSsoApplication(SsoApplication Application)
        {
            DeleteSsoApplication(Application.ApplicationName);
            CreateSsoApplication(Application);
            SetConfigProperties(Application.ApplicationName, Application.PropertyBag);
        }

        public SsoApplication GetSsoApplication(string appName)
        {
            int flags;
            int count;
            var props = new SsoPropertyBag();
            string description, contactInfo, appUserAcct, appAdminAcct;
            string prefixedApplicationName;
            if (appName.StartsWith(identifierGuid))
            {
                prefixedApplicationName = appName;
            }
            else
            {
                prefixedApplicationName = identifierGuid + "_" + appName;
            }

            //get config info
            ISSOAdmin ssoAdmin = new ISSOAdmin();
            ssoAdmin.GetApplicationInfo(prefixedApplicationName, out description, out contactInfo, out appUserAcct, out appAdminAcct, out flags, out count);

            //get properties
            var configStore = new ISSOConfigStore();
            var propertiesBag = new SsoPropertyBag();
            var SsoApp = new SsoApplication
            {
                AdminGroup = appAdminAcct,
                ApplicationName = appName,
                Description = description,
                UserGroup = appUserAcct,
                PropertyBag = new List<SsoConfigurationField>()
            };

            configStore.GetConfigInfo(prefixedApplicationName, identifierGuid, SSOFlag.SSO_FLAG_RUNTIME, props);
            foreach (DictionaryEntry p in props.properties)
            {
                var field = new SsoConfigurationField
                {
                    ConfigurationKey = Convert.ToString(p.Key),
                    ConfigurationValue = Convert.ToString(p.Value)
                };
                SsoApp.PropertyBag.Add(field);
            }

            return SsoApp;
        }
        
        public void DeleteSsoApplication(string appName)
        {
            string prefixedApplicationName;
            if (appName.StartsWith(identifierGuid))
            {
                prefixedApplicationName = appName;
            }
            else
            {
                prefixedApplicationName = identifierGuid + "_" + appName;
            }

            var ssoAdmin = new ISSOAdmin();
            ssoAdmin.DeleteApplication(prefixedApplicationName);
        }

        public void SetConfigProperties(string ApplicationName, List<SsoConfigurationField> ApplicationProperties)
        {
            string prefixedApplicationName;
            if (ApplicationName.StartsWith(identifierGuid))
            {
                prefixedApplicationName = ApplicationName;
            }
            else
            {
                prefixedApplicationName = identifierGuid + "_" + ApplicationName;
            }

            var configStore = new ISSOConfigStore();
            var propBag = new SsoPropertyBag();
            
            foreach (var item in ApplicationProperties)
            {
                object keyValue = item.ConfigurationValue;
                propBag.Write(item.ConfigurationKey, ref keyValue);
            }
            configStore.SetConfigInfo(prefixedApplicationName, identifierGuid, propBag);
        }

        public bool AppExists(string appName)
        {
            string prefixedApplicationName;
            if (appName.StartsWith(identifierGuid))
            {
                prefixedApplicationName = appName;
            }
            else
            {
                prefixedApplicationName = identifierGuid + "_" + appName;
            }
            var ssoa = GetAdmin();
            string description;
            string contact;
            string grpName;
            string grpAdmin;
            int flags;
            int fields;
            bool ret = false;
            try
            {
                ssoa.GetApplicationInfo(prefixedApplicationName, out description, out contact, out grpName, out grpAdmin, out flags, out fields);
                ret = true;
            }
            catch { }
            finally
            {
                if (ssoa != null)
                    Marshal.ReleaseComObject(ssoa);
            }
            return ret;
        }

        public string ExportSsoApplication(string ApplicationName, bool Encrypt)
        {
            var appToExport = GetSsoApplication(ApplicationName);

            // Serialize to Xml
            var ConfigXml = this.SerializeConfiguration(appToExport);
            return ConfigXml.OuterXml;
        }

        public string Encrypt(string clearText, string Key)
        {
            if (string.IsNullOrEmpty(clearText))
                throw new ArgumentNullException("clearText");
            if (string.IsNullOrEmpty(Key))
                throw new ArgumentNullException("Key");

            string outStr = null;                       // Encrypted string to return 
            RijndaelManaged aesAlg = null;              // RijndaelManaged object used to encrypt the data. 

            try
            {
                // generate the key from the shared secret and the salt 
                var key = new Rfc2898DeriveBytes(Key, _salt);

                // Create a RijndaelManaged object 
                // with the specified key and IV. 
                aesAlg = new RijndaelManaged();
                aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8);
                aesAlg.IV = key.GetBytes(aesAlg.BlockSize / 8);

                // Create a decrytor to perform the stream transform. 
                ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

                // Create the streams used for encryption. 
                using (var msEncrypt = new MemoryStream())
                {
                    using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                    {
                        using (var swEncrypt = new StreamWriter(csEncrypt))
                        {
                            //Write all data to the stream. 
                            swEncrypt.Write(clearText);
                        }
                    }
                    outStr = Convert.ToBase64String(msEncrypt.ToArray());
                }
            }
            finally
            {
                // Clear the RijndaelManaged object. 
                if (aesAlg != null)
                    aesAlg.Clear();
            }

            // Return the encrypted bytes from the memory stream. 
            return outStr;

        }

        public string Decrypt(string cipherText, string Key)
        {
            if (string.IsNullOrEmpty(cipherText))
                throw new ArgumentNullException("cipherText");
            if (string.IsNullOrEmpty(Key))
                throw new ArgumentNullException("Key");

            // Declare the RijndaelManaged object 
            // used to decrypt the data. 
            RijndaelManaged aesAlg = null;

            // Declare the string used to hold 
            // the decrypted text. 
            string plaintext = null;

            try
            {
                // generate the key from the shared secret and the salt 
                var key = new Rfc2898DeriveBytes(Key, _salt);

                // Create a RijndaelManaged object 
                // with the specified key and IV. 
                aesAlg = new RijndaelManaged();
                aesAlg.Key = key.GetBytes(aesAlg.KeySize / 8);
                aesAlg.IV = key.GetBytes(aesAlg.BlockSize / 8);

                // Create a decrytor to perform the stream transform. 
                ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
                // Create the streams used for decryption.                 
                byte[] bytes = Convert.FromBase64String(cipherText);
                using (var msDecrypt = new MemoryStream(bytes))
                {
                    using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                    {
                        using (var srDecrypt = new StreamReader(csDecrypt))

                            // Read the decrypted bytes from the decrypting stream 
                            // and place them in a string. 
                            plaintext = srDecrypt.ReadToEnd();
                    }
                }
            }
            finally
            {
                // Clear the RijndaelManaged object. 
                if (aesAlg != null)
                    aesAlg.Clear();
            }

            return plaintext;
        }

        public XmlDocument SerializeConfiguration(SsoApplication ApplicationConfiguration)
        {
            var ser = new DataContractSerializer(typeof(SsoApplication));
            var xdoc = new XmlDocument();

            using (var stream = new MemoryStream())
            {
                ser.WriteObject(stream, ApplicationConfiguration);
                stream.Flush();
                stream.Seek(0, SeekOrigin.Begin);
                xdoc.Load(stream);
            }
            return xdoc;
        }

        public SsoApplication DeserializeConfiguration(XmlDocument XmlConfig)
        {
            var AppConfig = new SsoApplication();
            var serializer = new DataContractSerializer(typeof(SsoApplication));
            var reader = new XmlNodeReader(XmlConfig.DocumentElement);
            AppConfig = (SsoApplication)serializer.ReadObject(reader);
            return AppConfig;
        }

        public object ReadKey(string appName, string propName)
        {
            try
            {
                string prefixedApplicationName = identifierGuid + "_" + appName;
                var ssoStore = new SSOConfigStore();
                var appMgmtBag = new SsoPropertyBag();
                ((ISSOConfigStore)ssoStore).GetConfigInfo(prefixedApplicationName, identifierGuid, SSOFlag.SSO_FLAG_RUNTIME, (IPropertyBag)appMgmtBag);
                object propertyValue = null;
                appMgmtBag.Read(propName, out propertyValue, 0);
                if (propertyValue == null)
                {
                    throw new System.MissingFieldException("Key has a null or empty value. Application Name = "
                        + prefixedApplicationName + ", Key Name = " + propName);
                }
                return propertyValue;
            }
            catch (Exception e)
            {
                System.Diagnostics.Trace.WriteLine(e.Message);
                throw;
            }
        }

        public void WriteKey(string appName, string propName, string propValue)
        {
            try
            {
                string prefixedApplicationName = identifierGuid + "_" + appName;
                var ssoStore = new SSOConfigStore();
                var appMgmtBag = new SsoPropertyBag();
                var ssoApp = GetSsoApplication(appName);
                foreach (var field in ssoApp.PropertyBag)
                {
                    if (field.ConfigurationKey.ToUpper() == propName.ToUpper())
                    {
                        field.ConfigurationValue = propValue;
                        break;
                    }

                    object currentValue = field.ConfigurationValue;
                    appMgmtBag.Write(field.ConfigurationKey, ref currentValue);
                }
                ((ISSOConfigStore)ssoStore).SetConfigInfo(prefixedApplicationName, identifierGuid, (IPropertyBag)appMgmtBag);
            }
            catch (Exception e)
            {
                System.Diagnostics.Trace.WriteLine(e.Message);
                throw;
            }
        }

        static ISSOAdmin GetAdmin()
        {
            var ssoadmin = new SSOAdmin();
            ISSOAdmin ssoa = (ISSOAdmin)ssoadmin;
            return ssoa;
        }
    }
}
